using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

namespace org.jawk.jrt
{
	/// <summary>The Jawk runtime coordinator.</summary>
	/// <remarks>
	/// The Jawk runtime coordinator.
	/// The JRT services interpreted and compiled Jawk scripts, mainly
	/// for IO and other non-CPU bound tasks.  The goal is to house
	/// service functions into a Java-compiled class rather than
	/// to hand-craft service functions in bytecode, or cut-paste
	/// compiled JVM code into the compiled AWK script.  Also,
	/// since these functions are non-CPU bound, the need for
	/// inlining is reduced.
	/// <p>
	/// Variable access is achieved through the VariableManager interface.
	/// The constructor requires a VariableManager instance (which, in
	/// this case, is the compiled Jawk class itself).
	/// <p>
	/// Main services include:
	/// <ul>
	/// <li>File and command output redirection via print(f).
	/// <li>File and command input redirection via getline.
	/// <li>Most built-in AWK functions, such as system(), sprintf(), etc.
	/// <li>Automatic AWK type conversion routines.
	/// <li>IO management for input rule processing.
	/// <li>Random number engine management.
	/// <li>Input field ($0, $1, ...) management.
	/// </ul>
	/// <p>
	/// All static and non-static service methods should be package-private
	/// to the resultant AWK script class rather than public.  However,
	/// the resultant script class is not in the org.jawk.jrt package
	/// by default, and the user may reassign the resultant script class
	/// to another package.  Therefore, all accessed methods are public.
	/// </remarks>
	/// <seealso cref="IVariableManager">IVariableManager</seealso>
	public class JRT
	{
		/// <summary>The default regular expression for RS.</summary>
		/// <remarks>
		/// The default regular expression for RS.
		/// The AVM refers to this field, so that the field exists
		/// in one place.
		/// </remarks>
		public static readonly string DEFAULT_RS_REGEX = null;

		static JRT()
		{
            DEFAULT_RS_REGEX = ConfigurationManager.AppSettings["line.separator"];
            if (string.IsNullOrEmpty(DEFAULT_RS_REGEX))
            {
                DEFAULT_RS_REGEX = "(\n)|(\r\n)";
            }
		}

		private org.jawk.jrt.IVariableManager vm;

		/// <summary>Create a JRT with a VariableManager</summary>
		/// <param name="vm">The VariableManager to use with this JRT.</param>
		public JRT(org.jawk.jrt.IVariableManager vm)
		{
			// There must be NO imports to org.jawk.*,
			// other than org.jawk.jrt which occurs by
			// default.  We wish to house all
			// required runtime classes in jrt.jar,
			// not have to refer to jawk.jar!
			//public static final String DEFAULT_RS_REGEX = "(\n)|(\r\n)";
			//assert DEFAULT_RS_REGEX != null : "line.separator not found in System properties ?!";
			this.vm = vm;
		}

		/// <summary>Assign all -v variables.</summary>
		/// <remarks>Assign all -v variables.</remarks>
		/// <param name="initial_var_map">
		/// A map containing all initial variable
		/// names and their values.
		/// </param>
		public void AssignInitialVariables(System.Collections.Generic.IDictionary<string, 
			object> initial_var_map)
		{
			//assert initial_var_map != null;
			//System.out.println("initial_var_map = "+initial_var_map);
			foreach (string name in initial_var_map.Keys)
			{
				vm.AssignVariable(name, initial_var_map[name]);
			}
		}

		/// <summary>
		/// Called by AVM/compiled modules to assign local
		/// environment variables to an associative array
		/// (in this case, to ENVIRON).
		/// </summary>
		/// <remarks>
		/// Called by AVM/compiled modules to assign local
		/// environment variables to an associative array
		/// (in this case, to ENVIRON).
		/// </remarks>
		/// <param name="aa">
		/// The associatve array to populate with
		/// environment variables.  The module asserts that
		/// the associative array is empty prior to population.
		/// </param>
		public static void AssignEnvironmentVariables(org.jawk.jrt.AssocArray aa)
		{
			//assert aa.keySet().size() == 0;
            System.Collections.IDictionary env = Environment.GetEnvironmentVariables();
                
			foreach (string key in env.Keys)
			{
				aa.Put(key, env[key]);
			}
		}

		/// <summary>
		/// Convert Strings, Integers, and Doubles to Strings
		/// based on the CONVFMT variable contents.
		/// </summary>
		/// <remarks>
		/// Convert Strings, Integers, and Doubles to Strings
		/// based on the CONVFMT variable contents.
		/// </remarks>
		/// <param name="o">Object to convert.</param>
		/// <param name="convfmt">The contents of the CONVFMT variable.</param>
		/// <returns>A String representation of o.</returns>
		public static string ToAwkString(object o, string convfmt)
		{
            Regex integerRegex = new Regex("^[0-9]+$");
            Regex doubleRegex = new Regex("^[0-9]*([.,][0-9]+)?$");

            string value = o.ToString();

            int intValue = 0;
            double doubleValue = 0;
            if (integerRegex.IsMatch(value) && int.TryParse(value, out intValue))
            {
                return intValue.ToString(System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
            }
            else if (doubleRegex.IsMatch(value) && double.TryParse(o.ToString(), out doubleValue))
            {
                return doubleValue.ToString(convfmt, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
            }
			else
			{
				return o.ToString();
			}
		}

		// not static to use CONVFMT (& possibly OFMT later)
		/// <summary>
		/// Convert a String, Integer, or Double to String
		/// based on the OFMT variable contents.
		/// </summary>
		/// <remarks>
		/// Convert a String, Integer, or Double to String
		/// based on the OFMT variable contents.  Jawk will
		/// subsequently use this String for output via print().
		/// </remarks>
		/// <param name="o">Object to convert.</param>
		/// <param name="ofmt">The contents of the OFMT variable.</param>
		/// <returns>A String representation of o.</returns>
		public static string ToAwkStringForOutput(object o, string ofmt)
		{
            int intValue = 0;
            double doubleValue = 0;
            if (int.TryParse(o.ToString(), out intValue))
            {
                return intValue.ToString(System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
            }
            else if (double.TryParse(o.ToString(), out doubleValue))
            {
                return doubleValue.ToString(ofmt, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
            }
            else
            {
                return o.ToString();
            }
		}

		public static double ToDouble(object o)
		{
            double doubleValue = 0;
			if (double.TryParse(o.ToString(),out doubleValue))
			{
				return doubleValue;
			}
			else
			{
                return 0;
			}
		}

		/// <summary>Compares two objects.</summary>
		/// <remarks>
		/// Compares two objects.  Whether to employ less-than, equals, or
		/// greater-than checks depends on the mode chosen by the callee.
		/// It handles Awk variable rules and type conversion semantics.
		/// </remarks>
		/// <param name="o1">The 1st object.</param>
		/// <param name="o2">the 2nd object.</param>
		/// <param name="mode">
		/// <ul>
		/// <li>&lt; 0 - Return true if o1 &lt; o2.
		/// <li>0 - Return true if o1 == o2.
		/// <li>&gt; 0 - Return true if o1 &gt; o2.
		/// </ul>
		/// </param>
		public static bool Compare2(object o1, object o2, int mode)
		{
            double d1 = 0; 
            double d2 = 0;

            if (double.TryParse(o1.ToString(), out d1) && double.TryParse(o2.ToString(), out d2))
            {
                if (mode < 0)
                {
                    return d1 < d2;
                }
                else if (mode == 0)
                {
                    return d1 == d2;
                }
                else
                {
                    return d1 > d2;
                }
            }
            else
            {
                // string equality usually occurs more often than natural ordering comparison
                if (mode == 0)
                {
                    return (o1.ToString().Equals(o2.ToString()));
                }
                else if (mode < 0)
                {
                    return o1.ToString().CompareTo(o2.ToString()) < 0;
                }
                else
                {
                    return o1.ToString().CompareTo(o2.ToString()) > 0;
                }
            }
		}

		/// <summary>
		/// Return an object which is numerically equivalent to
		/// one plus a given object.
		/// </summary>
		/// <remarks>
		/// Return an object which is numerically equivalent to
		/// one plus a given object.  For Integers and Doubles,
		/// this is similar to o+1.  For Strings, attempts are
		/// made to convert it to a double first.  If the
		/// String does not represent a valid Double, 1 is returned.
		/// </remarks>
		/// <param name="o">The object to increase.</param>
		/// <returns>
		/// o+1 if o is an Integer or Double object, or
		/// if o is a String object and represents a double.
		/// Otherwise, 1 is returned.  If the return value
		/// is an integer, an Integer object is returned.
		/// Otherwise, a Double object is returned.
		/// </returns>
		public static object Inc(object o)
		{
			//assert(o != null);
			double ans;
			if (double.TryParse(o.ToString(),out ans))
			{
                ans++;
			}
			else
			{
                ans = 1;
			}

            
			if (ans == (int)ans)
			{
				return (int)ans;
			}
			else
			{
				return ans;
			}
		}

		/// <summary>
		/// Return an object which is numerically equivalent to
		/// one minus a given object.
		/// </summary>
		/// <remarks>
		/// Return an object which is numerically equivalent to
		/// one minus a given object.  For Integers and Doubles,
		/// this is similar to o-1.  For Strings, attempts are
		/// made to convert it to a double first.  If the
		/// String does not represent a valid Double, -1 is returned.
		/// </remarks>
		/// <param name="o">The object to increase.</param>
		/// <returns>
		/// o-1 if o is an Integer or Double object, or
		/// if o is a String object and represents a double.
		/// Otherwise, -1 is returned.  If the return value
		/// is an integer, an Integer object is returned.
		/// Otherwise, a Double object is returned.
		/// </returns>
		public static object Dec(object o)
		{
            double ans;
            if (double.TryParse(o.ToString(), out ans))
            {
                ans--;
            }
            else
            {
                ans = -1;
            }


            if (ans == (int)ans)
            {
                return (int)ans;
            }
            else
            {
                return ans;
            }
		}

		// non-static to reference "input_line"
		/// <summary>
		/// Converts an Integer, Double, String, Pattern,
		/// or PatternPair to a boolean.
		/// </summary>
		/// <remarks>
		/// Converts an Integer, Double, String, Pattern,
		/// or PatternPair to a boolean.
		/// </remarks>
		/// <param name="o">The object to convert to a boolean.</param>
		/// <returns>
		/// For the following class types for o:
		/// <ul>
		/// <li><strong>Integer</strong> - o.intValue() != 0
		/// <li><strong>Double</strong> - o.doubleValue() != 0
		/// <li><strong>String</strong> - o.length() &gt; 0
		/// <li><strong>Pattern</strong> - $0 ~ o
		/// <li><strong>PatternPair</strong> - $0 ~ o (with some state information)
		/// </ul>
		/// If o is none of these types, an error is thrown.
		/// </returns>
		/// <seealso cref="PatternPair">PatternPair</seealso>
		public bool ToBoolean(object o)
		{
			bool val;
			if (o is int)
			{
				val = ((int)o) != 0;
			}
			else
			{
				if (o is double)
				{
					val = Math.Round((double)o,6) != 0;
				}
				else
				{
					if (o is string)
					{
						val = (o.ToString().Length > 0);
					}
					else
					{
						if (o is Regex)
						{
							// match against $0
							// ...
							Regex pattern = (Regex)o;
							string s = input_line == null ? BLANK : input_line;
							val = pattern.IsMatch(s);
						}
						else
						{
							if (o is org.jawk.jrt.PatternPair)
							{
								string s = input_line == null ? BLANK : input_line;
								val = ((org.jawk.jrt.PatternPair)o).Matches(s);
							}
							else
							{
								throw new System.Exception("Unknown operand_stack type: " + o.GetType() + " for value " + o);
							}
						}
					}
				}
			}
			return val;
		}

		/// <summary>
		/// Splits the string into parts separated by one or more spaces;
		/// blank first and last fields are eliminated.
		/// </summary>
		/// <remarks>
		/// Splits the string into parts separated by one or more spaces;
		/// blank first and last fields are eliminated.
		/// This conforms to the 2-argument version of AWK's split function.
		/// </remarks>
		/// <param name="array">The array to populate.</param>
		/// <param name="string">The string to split.</param>
		/// <param name="convfmt">Contents of the CONVFMT variable.</param>
		/// <returns>The number of parts resulting from this split operation.</returns>
		public static int Split(object array, object @string, string convfmt)
		{
			return SplitWorker(new java.util.StringTokenizer(ToAwkString(@string, convfmt)), 
				(org.jawk.jrt.AssocArray)array);
		}

		/// <summary>Splits the string into parts separated the regular expression fs.</summary>
		/// <remarks>
		/// Splits the string into parts separated the regular expression fs.
		/// This conforms to the 3-argument version of AWK's split function.
		/// <p>
		/// If fs is blank, it behaves similar to the 2-arg version of
		/// AWK's split function.
		/// </remarks>
		/// <param name="fs">Field separator regular expression.</param>
		/// <param name="array">The array to populate.</param>
		/// <param name="string">The string to split.</param>
		/// <param name="convfmt">Contents of the CONVFMT variable.</param>
		/// <returns>The number of parts resulting from this split operation.</returns>
		public static int Split(object fs, object array, object @string, string convfmt)
		{
			string fs_string = ToAwkString(fs, convfmt);
			if (fs_string.Equals(" "))
			{
				return SplitWorker(new java.util.StringTokenizer(ToAwkString(@string, convfmt)), 
					(org.jawk.jrt.AssocArray)array);
			}
			else
			{
				if (fs_string.Equals(string.Empty))
				{
					return SplitWorker(new org.jawk.jrt.CharacterTokenizer(ToAwkString(@string, convfmt
						)), (org.jawk.jrt.AssocArray)array);
				}
				else
				{
					if (fs_string.Length == 1)
					{
						return SplitWorker(new org.jawk.jrt.SingleCharacterTokenizer(ToAwkString(@string, 
							convfmt), fs_string[0]), (org.jawk.jrt.AssocArray)array);
					}
					else
					{
						return SplitWorker(new org.jawk.jrt.RegexTokenizer(ToAwkString(@string, convfmt), 
							fs_string), (org.jawk.jrt.AssocArray)array);
					}
				}
			}
		}

		private static int SplitWorker(java.util.IEnumeration<object> e, org.jawk.jrt.AssocArray aa)
		{
			int cnt = 0;
			aa.Clear();

			while (e.HasMoreElements())
			{
				aa.Put(++cnt, e.NextElement());
			}
			return cnt;
		}

		public org.jawk.jrt.PartitioningReader pr = null;

		public string input_line = null;

		private System.Collections.Generic.IList<string> input_fields = new System.Collections.Generic.List
			<string>(100);

		private org.jawk.jrt.AssocArray arglist_aa = null;

		private int arglist_idx;

		private bool has_filenames = false;

		private static readonly string BLANK = string.Empty;

		// Paritioning reader for stdin.
		// Current input line ($0).
		// Current input fields ($0, $1, $2, ...).
		/// <summary>
		/// Attempt to consume one line of input, either from stdin
		/// or from filenames passed in to ARGC/ARGV via
		/// the command-line.
		/// </summary>
		/// <remarks>
		/// Attempt to consume one line of input, either from stdin
		/// or from filenames passed in to ARGC/ARGV via
		/// the command-line.
		/// </remarks>
		/// <param name="for_getline">true if call is for getline, false otherwise.</param>
		/// <returns>true if line is consumed, false otherwise.</returns>
		/// <exception cref="System.IO.IOException">upon an IO error.</exception>
		public virtual bool JrtConsumeInput(bool for_getline)
		{
			// first time!
			if (arglist_aa == null)
			{
				object arglist_obj = vm.GetARGV();
				// vm.getVariable("argv_field", true);
				arglist_aa = (org.jawk.jrt.AssocArray)arglist_obj;
				arglist_idx = 1;
				// calculate has_filenames
				int argc = (int)ToDouble(vm.GetARGC());
				//(vm.getVariable("argc_field", true));
				// 1 .. argc doesn't make sense
				// 1 .. argc-1 does since arguments of:
				// a b c
				// result in:
				// ARGC=4
				// ARGV[0]="java Awk"
				// ARGV[1]="a"
				// ARGV[2]="b"
				// ARGV[3]="c"
				for (int i = 1; i < argc; i++)
				{
					if (arglist_aa.IsIn(i))
					{
						object namevalue_or_filename_object = arglist_aa.Get(i);
						string namevalue_or_filename = ToAwkString(namevalue_or_filename_object, vm.GetCONVFMT
							().ToString());
						if (namevalue_or_filename.IndexOf("=") == -1)
						{
							// filename!
							has_filenames = true;
							break;
						}
					}
				}
			}
			// initial: pr == null
			// subsequent: pr != null, but eof
			while (true)
			{
				try
				{
					if (pr == null)
					{
						int argc = (int)ToDouble(vm.GetARGC());
						// (vm.getVariable("argc_field", true));
						object o = BLANK;
						while (arglist_idx <= argc)
						{
							o = arglist_aa.Get(arglist_idx);
							++arglist_idx;
							if (!o.ToString().Equals(BLANK))
							{
								break;
							}
						}
						if (!o.Equals(BLANK))
						{
                            if (o is Stream)
                            {
                                pr = new org.jawk.jrt.PartitioningReader((Stream)o, vm.GetRS().ToString(), true);
                                vm.SetFILENAME("MemoryStream");
                                vm.ResetFNR();
                            }
                            else
                            {
                                string name_value_or_filename = ToAwkString(o, vm.GetCONVFMT().ToString());
                                if (name_value_or_filename.IndexOf('=') == -1)
                                {
                                    pr = new org.jawk.jrt.PartitioningReader(new FileStream(name_value_or_filename, FileMode.Open, FileAccess.Read), vm.GetRS().ToString(), true);
                                    vm.SetFILENAME(name_value_or_filename);
                                    vm.ResetFNR();
                                }
                                else
                                {
                                    SetFilelistVariable(name_value_or_filename);
                                    if (!has_filenames)
                                    {
                                        // stdin with a variable!
                                        pr = new org.jawk.jrt.PartitioningReader(new StreamReader(Console.OpenStandardInput()), vm.GetRS().ToString());
                                        vm.SetFILENAME(string.Empty);
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                            }
                        }
						else
						{
							if (!has_filenames)
							{
                                pr = new org.jawk.jrt.PartitioningReader(new StreamReader(Console.OpenStandardInput()), vm.GetRS().ToString());
								vm.SetFILENAME(string.Empty);
							}
							else
							{
								return false;
							}
						}
					}
					else
					{
						if (input_line == null)
						{
							if (has_filenames)
							{
								int argc = (int)ToDouble(vm.GetARGC());
								object o = BLANK;
								while (arglist_idx <= argc)
								{
									o = arglist_aa.Get(arglist_idx);
									++arglist_idx;
									if (!o.ToString().Equals(BLANK))
									{
										break;
									}
								}
								if (!o.Equals(BLANK))
								{
                                    if (o is Stream)
                                    {
                                        pr = new org.jawk.jrt.PartitioningReader((Stream)o, vm.GetRS().ToString(), true);
                                        vm.SetFILENAME("MemoryStream");
                                        vm.ResetFNR();
                                    }
                                    else
                                    {
                                        string name_value_or_filename = ToAwkString(o, vm.GetCONVFMT().ToString());
                                        if (name_value_or_filename.IndexOf('=') == -1)
                                        {
                                            // true = from filename list
                                            pr = new org.jawk.jrt.PartitioningReader(new FileStream(name_value_or_filename, FileMode.Open), vm.GetRS().ToString(), true);
                                            vm.SetFILENAME(name_value_or_filename);
                                            vm.ResetFNR();
                                        }
                                        else
                                        {
                                            SetFilelistVariable(name_value_or_filename);
                                            vm.IncNR();
                                            continue;
                                        }
                                    }
                                }
								else
								{
									return false;
								}
							}
							else
							{
								return false;
							}
						}
					}
					// when active_input == false, usually means
					// to instantiate "pr" (partitioningreader for $0, etc)
					// for Jawk extensions
					//if (! active_input)
					//	return false;
					input_line = pr.ReadRecord();
					if (input_line == null)
					{
						continue;
					}
					else
					{
						if (for_getline)
						{
						}
						else
						{
							// TRUE
							// leave result on the stack
							// DO NOTHING!  The callee will re-acquire $0
							// FALSE
							// leave the stack alone ...
							JrtParseFields();
						}
						vm.IncNR();
						if (pr.FromFilenameList())
						{
							vm.IncFNR();
						}
						return true;
					}
				}
				catch (System.IO.IOException ioe)
				{
					System.Console.Error.WriteLine("IO Exception: " + ioe);
					continue;
				}
			}
		}

		// public boolean jrtConsumeInput(boolean for_getline) throws IOException
		/// <exception cref="System.ArgumentException"></exception>
		private void SetFilelistVariable(string name_value)
		{
			int eq_idx = name_value.IndexOf('=');
			// variable name should be non-blank
			//assert eq_idx >= 0;
			if (eq_idx == 0)
			{
				throw new System.ArgumentException("Must have a non-blank variable name in a name=value variable assignment argument."
					);
			}
            string name = name_value.Substring(0, eq_idx);
            string value = name_value.Substring(eq_idx + 1);
			object obj;
			try
			{
				obj = int.Parse(value);
			}
			catch (FormatException)
			{
				try
				{
                    obj = double.Parse(value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
				}
				catch (FormatException)
				{
					obj = value;
				}
			}
			vm.AssignVariable(name, obj);
		}

		/// <summary>Splits $0 into $1, $2, etc.</summary>
		/// <remarks>
		/// Splits $0 into $1, $2, etc.
		/// Called when an update to $0 has occurred.
		/// </remarks>
		public virtual void JrtParseFields()
		{
			string fs_string = vm.GetFS().ToString();
			java.util.IEnumeration<object> tokenizer;
			if (fs_string.Equals(" "))
			{
				tokenizer = new java.util.StringTokenizer(input_line);
			}
			else
			{
				if (fs_string.Length == 1)
				{
					tokenizer = new org.jawk.jrt.SingleCharacterTokenizer(input_line, fs_string[0]);
				}
				else
				{
					if (fs_string.Equals(string.Empty))
					{
						tokenizer = new org.jawk.jrt.CharacterTokenizer(input_line);
					}
					else
					{
						tokenizer = new org.jawk.jrt.RegexTokenizer(input_line, fs_string);
					}
				}
			}
			//assert input_line != null;
			input_fields.Clear();
			input_fields.Add(input_line);
			// $0
			while (tokenizer.HasMoreElements())
			{
				input_fields.Add((string)tokenizer.NextElement());
			}
			// recalc NF
			RecalculateNF();
		}

		private void RecalculateNF()
		{
			vm.SetNF(input_fields.Count - 1);
		}

		private static int ToFieldNumber(object o)
		{
			int fieldnum;
			if (!int.TryParse(o.ToString(),out fieldnum))
			{
				try
				{
                    fieldnum = (int)double.Parse(o.ToString(), System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
				}
				catch (FormatException)
				{
					throw new System.Exception("Field $(" + o.ToString() + ") is incorrect.");
				}
			}
			return fieldnum;
		}

		/// <summary>Retrieve the contents of a particular input field.</summary>
		/// <remarks>Retrieve the contents of a particular input field.</remarks>
		/// <param name="fieldnum_obj">Object refering to the field number.</param>
		/// <returns>Contents of the field.</returns>
		public virtual object JrtGetInputField(object fieldnum_obj)
		{
			return JrtGetInputField(ToFieldNumber(fieldnum_obj));
		}

		public virtual object JrtGetInputField(int fieldnum)
		{
			if (fieldnum < input_fields.Count)
			{
				string retval = input_fields[fieldnum];
				//assert retval != null;
				return retval;
			}
			else
			{
				return BLANK;
			}
		}

		/// <summary>Stores value_obj into an input field.</summary>
		/// <remarks>Stores value_obj into an input field.</remarks>
		/// <param name="value_obj">The RHS of the assignment.</param>
		/// <param name="field_num">Object refering to the field number.</param>
		/// <returns>A string representation of value_obj.</returns>
		public virtual string JrtSetInputField(object value_obj, int field_num)
		{
			//assert field_num >= 1;
			//assert value_obj != null;
			string value = value_obj.ToString();
			// if the value is BLANK
			if (value.Equals(BLANK))
			{
				if (field_num < input_fields.Count)
				{
                    input_fields.RemoveAt(field_num);
					input_fields.Insert(field_num, BLANK);
				}
			}
			else
			{
				// append the list to accommodate the new value
				for (int i = input_fields.Count - 1; i < field_num; i++)
				{
					input_fields.Add(BLANK);
				}
                if (input_fields.Count > 0) input_fields.RemoveAt(field_num);
				input_fields.Insert(field_num, value);
			}
			// rebuild $0
			RebuildDollarZeroFromFields();
			// recalc NF
			RecalculateNF();
			return value;
		}

		private void RebuildDollarZeroFromFields()
		{
			System.Text.StringBuilder new_dollar_zero_sb = new System.Text.StringBuilder();
			string ofs = vm.GetOFS().ToString();
			for (int i = 1; i < input_fields.Count; i++)
			{
				if (i > 1)
				{
					new_dollar_zero_sb.Append(ofs);
				}
				new_dollar_zero_sb.Append(input_fields[i]);
			}
            if (input_fields.Count>0) input_fields.RemoveAt(0);
			input_fields.Insert(0, new_dollar_zero_sb.ToString());
		}

		private static readonly int ONE = 1;

		private static readonly int ZERO = 0;

		private static readonly int MINUS_ONE = -1;

		private string jrt_input_string;

		public virtual int JrtConsumeFileInputForGetline(string filename)
		{
			try
			{
				if (JrtConsumeFileInput(filename))
				{
					return ONE;
				}
				else
				{
					jrt_input_string = string.Empty;
					return ZERO;
				}
			}
			catch (System.IO.IOException)
			{
				jrt_input_string = string.Empty;
				return MINUS_ONE;
			}
		}

		/// <summary>
		/// Retrieve the next line of output from a command, executing
		/// the command if necessary and store it to $0.
		/// </summary>
		/// <remarks>
		/// Retrieve the next line of output from a command, executing
		/// the command if necessary and store it to $0.
		/// </remarks>
		/// <param name="cmd_string">The command to execute.</param>
		/// <returns>
		/// Integer(1) if successful, Integer(0) if no more
		/// input is available, Integer(-1) upon an IO error.
		/// </returns>
		public virtual int JrtConsumeCommandInputForGetline(string cmd_string)
		{
			try
			{
				if (JrtConsumeCommandInput(cmd_string))
				{
					return ONE;
				}
				else
				{
					jrt_input_string = string.Empty;
					return ZERO;
				}
			}
			catch (System.IO.IOException)
			{
				jrt_input_string = string.Empty;
				return MINUS_ONE;
			}
		}

		/// <summary>Retrieve $0.</summary>
		/// <remarks>Retrieve $0.</remarks>
		/// <returns>The contents of the $0 input field.</returns>
		public virtual string JrtGetInputString()
		{
			return jrt_input_string;
		}

		private System.Collections.Generic.Dictionary<string, org.jawk.jrt.PartitioningReader
			> file_readers = new System.Collections.Generic.Dictionary<string, org.jawk.jrt.PartitioningReader
			>();

		private System.Collections.Generic.Dictionary<string, org.jawk.jrt.PartitioningReader
			> command_readers = new System.Collections.Generic.Dictionary<string, org.jawk.jrt.PartitioningReader
			>();

        private System.Collections.Generic.Dictionary<string, System.Diagnostics.Process> command_processes
             = new System.Collections.Generic.Dictionary<string, System.Diagnostics.Process>();

		public System.Collections.Generic.Dictionary<string, System.IO.TextWriter> output_files
			 = new System.Collections.Generic.Dictionary<string, System.IO.TextWriter>();

		/// <summary>
		/// Retrieve the PrintStream which writes to a particular
		/// file, creating the PrintStream if necessary.
		/// </summary>
		/// <remarks>
		/// Retrieve the PrintStream which writes to a particular
		/// file, creating the PrintStream if necessary.
		/// </remarks>
		/// <param name="filename">The file which to write the contents of the PrintStream.</param>
		/// <param name="append">true to append to the file, false to overwrite the file.</param>
		public System.IO.TextWriter JrtGetPrintStream(string filename, bool append)
		{
			System.IO.TextWriter ps = null;
            if(output_files.ContainsKey(filename))
            {
                ps = output_files[filename];
            }
			if (ps == null)
			{
				try
				{
					output_files[filename] = ps = new System.IO.StreamWriter(filename, append);
				}
				catch (System.IO.IOException ioe)
				{
					// true = autoflush
					throw new org.jawk.jrt.AwkRuntimeException("Cannot open " + filename + " for writing: "
						 + ioe);
				}
			}
			//assert ps != null;
			return ps;
		}

		/// <exception cref="System.IO.IOException"></exception>
		public virtual bool JrtConsumeFileInput(string filename)
		{
			org.jawk.jrt.PartitioningReader pr = null;
            if(file_readers.ContainsKey(filename))
            {
                pr = file_readers[filename];
            }
			if (pr == null)
			{
				try
				{
					file_readers[filename] = pr = new org.jawk.jrt.PartitioningReader(new StreamReader(filename), vm.GetRS().ToString());
					vm.SetFILENAME(filename);
				}
				catch (System.IO.IOException ioe)
				{
					System.Console.Error.WriteLine("IO Exception: " + ioe);
                    file_readers.Remove(filename);
					throw;
				}
			}
			input_line = pr.ReadRecord();
			if (input_line == null)
			{
				return false;
			}
			else
			{
				jrt_input_string = input_line;
				vm.IncNR();
				return true;
			}
		}

		/// <exception cref="System.IO.IOException"></exception>
		public virtual bool JrtConsumeCommandInput(string cmd)
		{
			org.jawk.jrt.PartitioningReader pr = null;
            if(command_readers.ContainsKey(cmd))
            {
                pr = command_readers[cmd];
            }
			if (pr == null)
			{
                try
                {
                    // spawn the process!
                    System.Diagnostics.Process p = System.Diagnostics.Process.Start("cmd.exe","/c "+cmd);

                    // no input to this process!
                    if(p.StandardOutput!=null) p.StandardInput.Close();

                    new org.jawk.jrt.DataPump(cmd + " [Error]", p.StandardError.BaseStream, System.Console.Error);
                    command_processes[cmd] = p;
                    command_readers[cmd] = pr = new org.jawk.jrt.PartitioningReader(p.StandardOutput, vm.GetRS().ToString());
                    vm.SetFILENAME(string.Empty);
                }
                catch (System.IO.IOException ioe)
                {
                    System.Console.Error.WriteLine("IO Exception: " + ioe);
                    command_readers.Remove(cmd);
                    System.Diagnostics.Process p = null;
                    if(command_processes.ContainsKey(cmd))
                    {
                        p = command_processes[cmd];
                        command_processes.Remove(cmd);
                    }
                    
                    if (p != null)
                    {
                        p.Kill();
                    }
                    throw;
                }
			}
			input_line = pr.ReadRecord();
			if (input_line == null)
			{
				return false;
			}
			else
			{
				jrt_input_string = input_line;
				vm.IncNR();
				return true;
			}
		}

		/// <summary>
		/// Retrieve the PrintStream which shuttles data to stdin for
		/// a process, executing the process if necessary.
		/// </summary>
		/// <remarks>
		/// Retrieve the PrintStream which shuttles data to stdin for
		/// a process, executing the process if necessary.
		/// Threads are created to shuttle the data to/from the
		/// process.
		/// </remarks>
		/// <param name="cmd">The command to execute.</param>
		/// <returns>
		/// The PrintStream which to write to provide
		/// input data to the process.
		/// </returns>
		public virtual System.IO.TextWriter JrtSpawnForOutput(string cmd)
		{
			System.IO.TextWriter ps = null;
            if(output_streams.ContainsKey(cmd))
            {
                ps = output_streams[cmd];
            }
			if (ps == null)
			{
				System.Diagnostics.Process p;
				try
				{
						// spawn the process!
                    p = System.Diagnostics.Process.Start("cmd.exe", "/c " + cmd);

                    new org.jawk.jrt.DataPump(cmd + " [Error]", p.StandardError.BaseStream, System.Console.Error);
                    new org.jawk.jrt.DataPump(cmd + " [Out]", p.StandardOutput.BaseStream, System.Console.Out);
				}
				catch (System.IO.IOException ioe)
				{
					throw new org.jawk.jrt.AwkRuntimeException("Can't spawn " + cmd + ": " + ioe);
				}
				output_processes[cmd] = p;
				output_streams[cmd] = ps = p.StandardInput;
			}
			// true = auto-flush
			return ps;
		}

		/// <summary>
		/// Attempt to close an open stream, whether it is
		/// an input file, output file, input process, or output
		/// process.
		/// </summary>
		/// <remarks>
		/// Attempt to close an open stream, whether it is
		/// an input file, output file, input process, or output
		/// process.
		/// <p>
		/// The specification did not describe AWK behavior
		/// when attempting to close streams/processes with
		/// the same file/command name.  In this case,
		/// <em>all</em> open streams with this name
		/// are closed.
		/// </remarks>
		/// <param name="filename">The filename/command process to close.</param>
		/// <returns>
		/// Integer(0) upon a successful close, Integer(-1)
		/// otherwise.
		/// </returns>
		public virtual int JrtClose(string filename)
		{
			bool b1 = JrtCloseFileReader(filename);
			bool b2 = JrtCloseCommandReader(filename);
			bool b3 = JrtCloseOutputFile(filename);
			bool b4 = JrtCloseOutputStream(filename);
			// either close will do
			return (b1 || b2 || b3 || b4) ? ZERO : MINUS_ONE;
		}

		public virtual void JrtCloseAll()
		{
			System.Collections.Generic.ICollection<string> set = new List<string>();
			foreach (string s in file_readers.Keys)
			{
				set.Add(s);
			}
			foreach (string s in command_readers.Keys)
			{
				set.Add(s);
			}
			foreach (string s in output_files.Keys)
			{
				set.Add(s);
			}
			foreach (string s in output_streams.Keys)
			{
				set.Add(s);
			}
			foreach (string s in set)
			{
				JrtClose(s);
			}
		}

		private bool JrtCloseOutputFile(string filename)
		{
			System.IO.TextWriter ps = null;
            if(output_files.ContainsKey(filename))
            {
                ps = output_files[filename];
            }
			if (ps != null)
			{
				ps.Close();
                output_files.Remove(filename);
			}
			return ps != null;
		}

		private bool JrtCloseOutputStream(string cmd)
		{
			System.Diagnostics.Process p = output_processes[cmd];
            System.IO.TextWriter ps = null;
            if (output_streams.ContainsKey(cmd))
            {
                ps = output_streams[cmd];
            }
			if (ps == null)
			{
				return false;
			}
			//assert p != null;
            output_processes.Remove(cmd);
            output_streams.Remove(cmd);
			ps.Close();
			// if windows, let the process kill itself eventually
			
				try
				{
					// causes a hard exit ?!
					p.WaitForExit();
					
                    //int rv2 = p.ExitValue();
				}
				catch (System.Exception ie)
				{
					throw new org.jawk.jrt.AwkRuntimeException("Caught exception while waiting for process exit: "
						 + ie);
				}
			
			return true;
		}

		private bool JrtCloseFileReader(string filename)
		{
			org.jawk.jrt.PartitioningReader pr = null;
            if(file_readers.ContainsKey(filename))
            {
                pr = file_readers[filename];
            }
			if (pr == null)
			{
				return false;
			}
            file_readers.Remove(filename);
			try
			{
				pr.Close();
				return true;
			}
			catch (System.IO.IOException)
			{
				return false;
			}
		}

		private bool JrtCloseCommandReader(string cmd)
		{
			System.Diagnostics.Process p = null;
            if(command_processes.ContainsKey(cmd))
            {
                p = command_processes[cmd];
            }

			org.jawk.jrt.PartitioningReader pr = null;
            if(command_readers.ContainsKey(cmd))
            {
                pr = command_readers[cmd];
            }
			if (pr == null)
			{
				return false;
			}
			//assert p != null;
            command_readers.Remove(cmd);
            command_processes.Remove(cmd);
			try
			{
				pr.Close();
				// let the process kill itself eventually
					try
					{
						// causes a hard die ?!
						p.WaitForExit();
						//int rv2 = p.ExitValue();
					}
					catch (System.Exception ie)
					{
						throw new org.jawk.jrt.AwkRuntimeException("Caught exception while waiting for process exit: "
							 + ie);
					}
				
				return true;
			}
			catch (System.IO.IOException)
			{
				return false;
			}
		}

		private System.Collections.Generic.IDictionary<string, System.Diagnostics.Process> output_processes
             = new System.Collections.Generic.Dictionary<string, System.Diagnostics.Process>();

		private System.Collections.Generic.IDictionary<string, System.IO.TextWriter> output_streams
			 = new System.Collections.Generic.Dictionary<string, System.IO.TextWriter>();

		/// <summary>
		/// Executes the command specified by cmd and waits
		/// for termination, returning an Integer object
		/// containing the return code.
		/// </summary>
		/// <remarks>
		/// Executes the command specified by cmd and waits
		/// for termination, returning an Integer object
		/// containing the return code.
		/// stdin to this process is closed while
		/// threads are created to shuttle stdout and
		/// stderr of the command to stdout/stderr
		/// of the calling process.
		/// </remarks>
		/// <param name="cmd">The command to execute.</param>
		/// <returns>
		/// Integer(return_code) of the created
		/// process.  Integer(-1) is returned on an IO error.
		/// </returns>
		public static int JrtSystem(string cmd)
		{
			try
			{
				// spawn the process!
                System.Diagnostics.ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", "/c " + cmd)
                                                              {
                                                                  UseShellExecute = false,
                                                                  RedirectStandardError = true,
                                                                  RedirectStandardInput = true,
                                                                  RedirectStandardOutput = true
                                                              };
                
                System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
				
				// no input to this process!
				p.StandardInput.Close();
                new org.jawk.jrt.DataPump(cmd + " [Error]", p.StandardError.BaseStream, System.Console.Error);
                new org.jawk.jrt.DataPump(cmd + " [Out]", p.StandardOutput.BaseStream, System.Console.Out);
				try
				{
					p.WaitForExit();
					return p.ExitCode;
				}
				catch (System.Exception)
				{
					return p.ExitCode;
				}
			}
			catch (System.IO.IOException ioe)
			{
				Console.Error.WriteLine(ioe);
				return MINUS_ONE;
			}
		}

		/// <summary>
		/// Applies a format string to a set of parameters and
		/// returns the formatted result.
		/// </summary>
		/// <remarks>
		/// Applies a format string to a set of parameters and
		/// returns the formatted result.
		/// String.format() is used to perform the formatting.
		/// Thus, an IllegalFormatException can be thrown.
		/// If so, a blank string ("") is returned.
		/// </remarks>
		/// <param name="arr">Arguments to format.</param>
		/// <param name="fmt_arg">The format string to apply.</param>
		/// <returns>
		/// The formatted string; a blank string
		/// if the format argument is invalid.
		/// </returns>
		/// <seealso cref="SprintfFunctionNoCatch(object[], string)">SprintfFunctionNoCatch(object[], string)
		/// 	</seealso>
		public static string SprintfFunction(object[] arr, string fmt_arg)
		{
			try
			{
				return string.Format(fmt_arg, arr);
			}
			catch (FormatException ex)
			{
				return string.Empty;
			}
		}

		/// <summary>
		/// Applies a format string to a set of parameters and
		/// prints the result to stdout.
		/// </summary>
		/// <remarks>
		/// Applies a format string to a set of parameters and
		/// prints the result to stdout.
		/// The implementation is a simple call to sprintfFunction:
		/// <blockquote>
		/// <pre>
		/// System.out.print(sprintfFunction(arr, fmt_arg));
		/// </pre>
		/// </blockquote>
		/// String.format() is used to perform the formatting.
		/// Thus, an IllegalFormatException can be thrown.
		/// If so, a blank string ("") is printed.
		/// </remarks>
		/// <param name="arr">Arguments to format.</param>
		/// <param name="fmt_arg">The format string to apply.</param>
		/// <seealso cref="PrintfFunctionNoCatch(object[], string)">PrintfFunctionNoCatch(object[], string)
		/// 	</seealso>
		public static void PrintfFunction(object[] arr, string fmt_arg)
		{
			System.Console.Out.Write(SprintfFunction(arr, fmt_arg));
		}

		/// <summary>
		/// Applies a format string to a set of parameters and
		/// prints the result to a PrintStream.
		/// </summary>
		/// <remarks>
		/// Applies a format string to a set of parameters and
		/// prints the result to a PrintStream.
		/// The implementation is a simple call to sprintfFunction:
		/// <blockquote>
		/// <pre>
		/// ps.print(sprintfFunction(arr, fmt_arg));
		/// </pre>
		/// </blockquote>
		/// String.format() is used to perform the formatting.
		/// Thus, an IllegalFormatException can be thrown.
		/// If so, a blank string ("") is printed.
		/// </remarks>
		/// <param name="ps">The PrintStream to use for printing.</param>
		/// <param name="arr">Arguments to format.</param>
		/// <param name="fmt_arg">The format string to apply.</param>
		/// <seealso cref="PrintfFunctionNoCatch(System.IO.TextWriter, object[], string)">PrintfFunctionNoCatch(System.IO.TextWriter, object[], string)
		/// 	</seealso>
		public static void PrintfFunction(System.IO.TextWriter ps, object[] arr, string fmt_arg
			)
		{
			ps.Write(SprintfFunction(arr, fmt_arg));
		}

		/// <exception cref="java.util.IllegalFormatException"></exception>
		public static string SprintfFunctionNoCatch(object[] arr, string fmt_arg)
		{
			return string.Format(fmt_arg, arr);
		}

		public static void PrintfFunctionNoCatch(object[] arr, string fmt_arg)
		{
			System.Console.Out.Write(SprintfFunctionNoCatch(arr, fmt_arg));
		}

		public static void PrintfFunctionNoCatch(System.IO.TextWriter ps, object[] arr, string
			 fmt_arg)
		{
			ps.Write(SprintfFunctionNoCatch(arr, fmt_arg));
		}

		public static int ReplaceFirst(object orig_value_obj, object repl_obj, object ere_obj
			, System.Text.StringBuilder sb, string convfmt)
		{
			string orig_value = ToAwkString(orig_value_obj, convfmt);
			string repl = ToAwkString(repl_obj, convfmt);
			string ere = ToAwkString(ere_obj, convfmt);

            Regex r = new Regex(ere);
            if (r.IsMatch(orig_value))
            {
                Match m = r.Match(orig_value);
                orig_value = orig_value.Remove(m.Index, m.Length).Insert(m.Index, repl);
            }

			sb.Length = 0;
            sb.Append(orig_value);
			
            if (sb.ToString().Equals(orig_value))
			{
				return ZERO;
			}
			else
			{
				return ONE;
			}
		}

		public static int ReplaceAll(object orig_value_obj, object repl_obj, object ere_obj
			, System.Text.StringBuilder sb, string convfmt)
		{
			string orig_value = ToAwkString(orig_value_obj, convfmt);
			string repl = ToAwkString(repl_obj, convfmt);
			string ere = ToAwkString(ere_obj, convfmt);
			// remove special meaning for backslash and dollar signs
			//repl = java.util.regex.Matcher.QuoteReplacement(repl);
			sb.Length = 0;

            int iPlace = 0;
            int cnt = 0;
            Regex r = new Regex(ere);
            while(r.IsMatch(orig_value,iPlace))
            {
                Match m = r.Match(orig_value,iPlace);
                cnt++;
                iPlace = m.Index + repl.Length;
                orig_value = orig_value.Remove(m.Index, m.Length).Insert(m.Index, repl);
            }

            sb.Append(orig_value);

            //java.util.regex.Pattern p = java.util.regex.Pattern.Compile(ere);
            //java.util.regex.Matcher m = p.Matcher(orig_value);
            //int cnt = 0;
            //while (m.Find())
            //{
            //    ++cnt;
            //    m.AppendReplacement(sb, repl);
            //}
            //m.AppendTail(sb);
			return cnt;
		}

		public static string Substr(object startpos_obj, string str)
		{
			int startpos = (int)ToDouble(startpos_obj);
			if (startpos <= 0)
			{
				throw new org.jawk.jrt.AwkRuntimeException("2nd arg to substr must be a positive integer"
					);
			}
			if (startpos > str.Length)
			{
				return string.Empty;
			}
			else
			{
                return str.Substring(startpos - 1);
			}
		}

		public static string Substr(object size_obj, object startpos_obj, string str)
		{
			int startpos = (int)ToDouble(startpos_obj);
			if (startpos <= 0)
			{
				throw new org.jawk.jrt.AwkRuntimeException("2nd arg to substr must be a positive integer"
					);
			}
			if (startpos > str.Length)
			{
				return string.Empty;
			}
			int size = (int)ToDouble(size_obj);
			if (size < 0)
			{
				throw new org.jawk.jrt.AwkRuntimeException("3nd arg to substr must be a non-negative integer"
					);
			}
			if (startpos + size > str.Length)
			{
                return str.Substring(startpos - 1);
			}
			else
			{
                return str.Substring(startpos - 1, startpos + size - 1);
			}
		}

		public static int TimeSeed()
		{
			long l = (new System.DateTime()).Ticks;
			long l2 = (l % (1000 * 60 * 60 * 24));
			int seed = (int)l2;
			return seed;
		}

		public static Random NewRandom(int seed)
		{
			return new Random(seed);
		}

		public virtual void ApplyRS(object rs_obj)
		{
			//	if (rs_obj.toString().equals(BLANK))
			//		rs_obj = DEFAULT_RS_REGEX;
			if (pr != null)
			{
				pr.RS(rs_obj.ToString());
			}
		}
	}
}
